/****************************************************************************
 * Copyright (C) 2013 Dj_Skual
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************/
#include "ENandSaveTask.hpp"
#include "../../FileOperations/Nand.hpp"
#include "../../FileOperations/fileops.h"
#include "../../FileOperations/DirList.h"
#include "../../Saves/SaveTools.h"
#include "../../Menu/Explorer.h"

ENandSaveTask::ENandSaveTask(short Type, u64 title_id, std::string tmppath)
	: tid(title_id)
	, tmpPath(tmppath)
	, type(Type)
	, canceled(false)
	, window(NULL)
{
	ThreadedTaskHandler::Instance()->AddTask(this);
}

void ENandSaveTask::OnCancelClick()
{
	canceled = true;
}

void ENandSaveTask::Execute(void)
{
	if(!tid || tmpPath.empty())
	{
		End(this, -1);
		return;
	}
	
	if(type == EXTRACT_TO_DEV)
		window = new ManageProgressWindow(tr("Extracting..."));
	else if(type == DELETE_FROM_ENAND)
		window = new ManageProgressWindow(tr("Deleting..."));
	else if(type == INSTALL_TO_NAND || type == INSTALL_TO_ENAND)
	{
		window = new ManageProgressWindow(tr("Installing..."));
		window->SetCancel(false);
	}
	
	if(type == EXTRACT_TO_DEV || type == DELETE_FROM_ENAND)
		window->Canceled.connect(this, &ENandSaveTask::OnCancelClick);
	
	int result = -1;
	if(type == DELETE_FROM_ENAND)
	{
		if(tmpPath[tmpPath.size()-1] != '/')
			tmpPath += "/";
		tmpPath += fmt("title/%08x/%08x", TITLE_UPPER(tid), TITLE_LOWER(tid));
		
		if(RemoveDirectory(tmpPath) > 0)
			result = 1;
	}
	else if(type == EXTRACT_TO_DEV)
	{
		result = ExtractESaveToDev();
	}
	else if(type == INSTALL_TO_NAND)
	{
		result = InstallESaveToNand();
	}
	else if(type == INSTALL_TO_ENAND)
	{
		result = InstallESaveToENand();
	}
	
	delete window;
	
	if(canceled)
		result = 0;
	
	End(this, result);
}

int ENandSaveTask::ExtractESaveToDev()
{
	std::string srcPath = CurrentBrowser()->GetCurrentPath();
	srcPath += fmt("/title/%08x/%08x/data", TITLE_UPPER(tid), TITLE_LOWER(tid));
	
	if(tmpPath.compare(tmpPath.size()-1, 1, "/"))
		tmpPath += "/";
	tmpPath += fmt("%016llx", tid);
	
	int result = -1;
	
	DirList * dirList = new DirList(srcPath, NULL, DirList::Files | DirList::Dirs | DirList::CheckSubfolders);
	if(dirList)
	{
		RemoveDirectory(tmpPath);
		
		result = 1;
		for(int i = 0; (i < dirList->GetFilecount()) && (result > 0) && (!canceled); i++)
		{
			if(!dirList->IsDir(i))
			{
				std::string tmp_dev = dirList->GetFilepath(i);
				tmp_dev.erase(0, srcPath.size());
				tmp_dev.insert(0, tmpPath);
				
				CreateSubfolder(GetParentDir(tmp_dev));
				result = CopyFile(dirList->GetFilepath(i), tmp_dev.c_str());
			}
		}
		
		delete dirList;
	}
	
	if(canceled)
		RemoveDirectory(tmpPath);
	
	return result;
}

int ENandSaveTask::InstallESaveToENand()
{
	int result = -1;
	
	std::string path = fmt("/title/%08x/%08x/data", TITLE_UPPER(tid), TITLE_LOWER(tid));
	
	std::string emuSrc = CurrentBrowser()->GetCurrentPath();
	emuSrc += path;
	std::string emuDest = tmpPath;
	emuDest += path;
	
	DirList * dirList = new DirList(emuSrc, NULL, DirList::Files | DirList::Dirs | DirList::CheckSubfolders);
	if(dirList)
	{
		RemoveDirectory(emuDest);
		
		result = 1;
		for(int i = 0; (i < dirList->GetFilecount()) && (result > 0); i++)
		{
			if(!dirList->IsDir(i))
			{
				std::string tmp_dest = dirList->GetFilepath(i);
				tmp_dest.erase(0, emuSrc.size());
				tmp_dest.insert(0, emuDest);
				
				CreateSubfolder(GetParentDir(tmp_dest));
				result = CopyFile(dirList->GetFilepath(i), tmp_dest.c_str());
			}
		}
		
		delete dirList;
	}
	
	return result;
}

int ENandSaveTask::InstallESaveToNand()
{
	std::string nandPath = SaveTools::GetNandPath(tid);
	int result = 1;
	
	CreatePathsList(nandPath);
	DeleteUselessNandFiles(nandPath);
	CreateMissingNandFiles();
	
	for(u32 i = 0; (i < isfsPath.size()) && (result == 1); i++)
	{
		result = -1;
		
		ByteArray buffer;
		if(LoadFileToMem(emuPath.at(i), buffer) > 0)
		{
			if(Nand::Write(isfsPath.at(i), buffer) > 0)
				result = 1;
		}
	}
	
	return result;
}

void ENandSaveTask::CreatePathsList(std::string nandPath)
{
	isfsPath.clear();
	emuPath.clear();
	
	tmpPath += fmt("/title/%08x/%08x/data", TITLE_UPPER(tid), TITLE_LOWER(tid));
	
	DirList * dirList = new DirList(tmpPath, NULL, DirList::Files | DirList::Dirs | DirList::CheckSubfolders);
	if(dirList)
	{
		for(int i = 0; i < dirList->GetFilecount(); i++)
		{
			if(!dirList->IsDir(i))
			{
				emuPath.push_back(dirList->GetFilepath(i));
				
				std::string tmpNand = dirList->GetFilepath(i);
				tmpNand = FixFileName(tmpNand);
				tmpNand.erase(0, tmpPath.size());
				tmpNand.insert(0, nandPath);
				
				isfsPath.push_back(tmpNand);
			}
		}
		delete dirList;
	}
}

void ENandSaveTask::DeleteUselessNandFiles(std::string path)
{
	std::vector<std::string> List;
	if( Nand::ReadDir(path, List) == ISFS_OK )
	{
		for (int i = 0; i < (int)List.size(); i++)
		{
			std::string nandPath = path;
			if(nandPath[nandPath.size()-1] != '/')
				nandPath += "/";
			nandPath += List.at(i);
			
			int fd = Nand::OpenRead(nandPath);
			if(fd < 0)
				DeleteUselessNandFiles(nandPath);
			else
			{
				Nand::Close(fd);
				bool need_file = false;
				for(u32 cur = 0; (cur < isfsPath.size()) && (!need_file); cur++)
				{
					if(!nandPath.compare(isfsPath.at(cur)))
						need_file = true;
				}
				if(!need_file)
					Nand::Delete(nandPath);
			}
		}
	}
}

void ENandSaveTask::CreateMissingNandFiles()
{
	for(u32 i = 0; i < isfsPath.size(); i++)
	{
		int fd = Nand::OpenRead(isfsPath.at(i));
		if(fd < 0)
			Nand::CreateFile(isfsPath.at(i), 0, 3, 3, 3);
		else
			Nand::Close(fd);
	}
}

std::string ENandSaveTask::FixFileName(std::string filename)
{
	filename.assign(StringReplace(filename, "&cl;", ":"));
	filename.assign(StringReplace(filename, "&st;", "*"));
	filename.assign(StringReplace(filename, "&qt;", "\""));
	filename.assign(StringReplace(filename, "&qm;", "?"));
	filename.assign(StringReplace(filename, "&vb;", "|"));
	filename.assign(StringReplace(filename, "&lt;", "<"));
	filename.assign(StringReplace(filename, "&gt;", ">"));
	
	return filename;
}
